/**
 * Copyright 2011 T. Spilman
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.jreg;

import java.math.BigInteger;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;

public enum Type {
	/**
	 * A null-terminated string. This will be either a Unicode or an ANSI
	 * string, depending on whether you use the Unicode or ANSI functions.
	 */
	REG_SZ {

		@Override
		public Object parse(String data) throws RegistryException {
			return data;
		}

		@Override
		public String prepare(Object value) {
			validate(value);
			return value.toString();
		}

		public void validate(Object value) throws IllegalArgumentException {
			if (value == null)
				throw new IllegalArgumentException("value must not be null");
			String s = value.toString();
			byte[] b = s.getBytes();
			if (b.length > MAX_LENGTH)
				throw new IllegalArgumentException(ERROR_MESSAGE);
		}

	},
	/**
	 * A sequence of null-terminated strings, terminated by an empty string
	 * (\0).
	 * 
	 * The following is an example:
	 * 
	 * String1\0String2\0String3\0LastString\0\0
	 * 
	 * The first \0 terminates the first string, the second to the last \0
	 * terminates the last string, and the final \0 terminates the sequence.
	 * Note that the final terminator must be factored into the length of the
	 * string.
	 */
	REG_MULTI_SZ {

		@Override
		public Object parse(String data) throws RegistryException {
			String[] multi = data.split("\0");
			String[] copy = new String[multi.length - 1];
			System.arraycopy(multi, 0, copy, 0, copy.length);
			return copy;
		}

		@Override
		public String prepare(Object value) {
			Preconditions.checkArgument(value instanceof String[]);
			String[] data = (String[]) value;
			String[] copy = new String[data.length + 1];
			System.arraycopy(data, 0, copy, 0, copy.length);
			copy[copy.length - 1] = "";
			return Joiner.on('\0').join(copy);
		}

	},
	/**
	 * A null-terminated string that contains unexpanded references to
	 * environment variables (for example, "%PATH%"). It will be a Unicode or
	 * ANSI string depending on whether you use the Unicode or ANSI functions.
	 */
	REG_EXPAND_SZ,
	/**
	 * A 32-bit number.
	 */
	REG_DWORD {

		@Override
		public Object parse(String data) throws RegistryException {
			return REG_QWORD.parse(data);
		}

		@Override
		public String prepare(Object value) throws RegistryException {
			return REG_QWORD.prepare(value);
		}

		@Override
		public void validate(Object value) throws IllegalArgumentException {
			REG_QWORD.validate(value);
		}

	},
	/**
	 * A 64-bit number.
	 */
	REG_QWORD {

		@Override
		public Object parse(String data) throws RegistryException {
			try {
				return new BigInteger(data);
			} catch (NumberFormatException e) {
				throw new RegistryException(e);
			}
		}

		@Override
		public String prepare(Object value) {
			validate(value);
			return value.toString();
		}

		@Override
		public void validate(Object value) throws IllegalArgumentException {
			Class<?>[] validClasses = { Integer.class, Long.class,
					BigInteger.class };
			for (Class<?> validClass : validClasses)
				if (validClass.isInstance(value))
					return;
			throw new IllegalArgumentException("unexpected value \"" + value
					+ "\"");
		}

	},
	/**
	 * Binary data in any form.
	 */
	REG_BINARY {

		@Override
		public Object parse(String data) throws RegistryException {
			char[] hex = data.toCharArray();
			try {
				return Hex.decodeHex(hex);
			} catch (DecoderException e) {
				throw new RegistryException(e);
			}
		}

		@Override
		public String prepare(Object value) {
			validate(value);
			byte[] data = (byte[]) value;
			char[] hex = Hex.encodeHex(data);
			return String.valueOf(hex);
		}

		@Override
		public void validate(Object value) throws IllegalArgumentException {
			if (!(value instanceof byte[]))
				throw new IllegalArgumentException("unexpected value \""
						+ value + "\"");
			byte[] data = (byte[]) value;
			if (data.length > MAX_LENGTH)
				throw new IllegalArgumentException(ERROR_MESSAGE);
		}

	},
	/**
	 * No defined value type.
	 */
	REG_NONE;

	/**
	 * Long values (more than 2,048 bytes) must be stored as files with the file
	 * names stored in the registry. This helps the registry perform
	 * efficiently. http://support.microsoft.com/kb/256986
	 */
	public static final long MAX_LENGTH = 2048;
	private static final String ERROR_MESSAGE = "Long values (more than 2,048 bytes) must be stored as files with the file names stored in the registry.";

	public Object parse(String data) throws RegistryException {
		throw new RegistryException("unsupported type");
	}

	public void validate(Object value) throws IllegalArgumentException {
		throw new RuntimeException("not yet implemented");
	}

	public String prepare(Object value) throws RegistryException {
		throw new RegistryException("unsupported type");
	}

	public static Type getDefault() {
		return REG_SZ;
	}

}